package org.xx.armory.swing;

import javax.swing.*;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;

import static java.lang.Math.abs;
import static java.util.Arrays.stream;
import static javax.swing.SwingUtilities.isEventDispatchThread;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notNull;
import static org.xx.armory.swing.components.FormatUtils.getDateFormat;

public final class UIUtils {
    /**
     * 用于在Swing事件处理线程中更新界面组件的执行器。
     */
    public final static Executor UPDATE_UI = new UpdateUIExecutor();
    /**
     * 界面上的最小日期。
     */
    public static final Date MIN_DATE = new Date(0);
    /**
     * 界面上的最大日期。
     */
    public static final Date MAX_DATE = new Date(32503852800000L);

    private UIUtils() {
    }

    /**
     * 获取指定日期的起始时刻。
     *
     * @param src
     *         指定日期。
     * @return 该日的起始时刻，时分秒和毫秒字段都被置为0，其它字段不变，如果参数{@code src}是{@code null}则返回最小日期。
     */
    public static Date floorOfDay(
            Date src
    ) {
        if (src == null) {
            return MIN_DATE;
        }

        final Calendar c = Calendar.getInstance();
        c.setTime(src);
        c.setLenient(true);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        return c.getTime();
    }

    /**
     * 获取指定日期的终止时刻。
     *
     * @param src
     *         指定日期。
     * @return 该日的起始时刻，时分秒和毫秒字段都被置为0，日期变为下一天，其它字段不变，如果参数{@code src}是{@code null}则返回最大日期。
     */
    public static Date ceilingOfDay(
            Date src
    ) {
        if (src == null) {
            return MAX_DATE;
        }

        final Calendar c = Calendar.getInstance();
        c.setTime(src);
        c.setLenient(true);
        c.set(Calendar.DAY_OF_MONTH, c.get(Calendar.DAY_OF_MONTH) + 1);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        return c.getTime();
    }

    public static Integer toIntegerObject(
            String s
    ) {
        s = trimToEmpty(s);
        if (s.isEmpty()) {
            return null;
        }

        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    public static Long toLongObject(
            String s
    ) {
        s = trimToEmpty(s);
        if (s.isEmpty()) {
            return null;
        }

        try {
            return Long.parseLong(s);
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    public static Double toDoubleObject(
            String s
    ) {
        s = trimToEmpty(s);
        if (s.isEmpty()) {
            return null;
        }

        try {
            return Double.parseDouble(s);
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    public static Date toDateObject(
            String s
    ) {
        s = trimToEmpty(s);
        if (s.isEmpty()) {
            return null;
        }

        try {
            return getDateFormat().parse(s);
        } catch (ParseException ex) {
            return null;
        }
    }

    /**
     * 将字符串转化为布尔值。
     * {@literal true, yes, on, 1, y, t}被转化为{@link Boolean#TRUE}。{@literal false, no, off, 0, n, f}被转化为{@link Boolean#FALSE}。其它被转化为{@code null}。
     *
     * @param s
     *         待转化的字符串，自动去掉首尾空格并转为小写。
     * @return 转化结果。
     */
    public static Boolean toBooleanObject(
            String s
    ) {
        s = trimToEmpty(s).toLowerCase();
        if ("true".equals(s) || "yes".equals(s) || "on".equals(s) || "1".equals(s) || "t".equals(s) || "y".equals(s)) {
            return true;
        } else if ("false".equals(s) || "no".equals(s) || "off".equals(s) || "0".equals(s) || "f".equals(s) || "n".equals(s)) {
            return false;
        } else {
            return null;
        }
    }

    public static BigDecimal toBigDecimalObject(
            String s
    ) {
        s = trimToEmpty(s);
        if (s.isEmpty()) {
            return null;
        }

        try {
            return new BigDecimal(s);
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    public static String[] toStringArray(
            Object[] src
    ) {
        if (src == null) {
            return null;
        }

        return stream(src).map(Object::toString).toArray(String[]::new);
    }

    /**
     * 判断两个值是否粗略相当。
     * <p>粗略相等指的是不考虑类型。</p>
     *
     * @param o1
     *         第一个参与比较的值。
     * @param o2
     *         第二个参与比较的值。
     * @return 是否粗略相等。
     */
    public static boolean equalsWidely(
            Object o1,
            Object o2
    ) {
        if (Objects.equals(o1, o2)) {
            return true;
        } else if (o1 instanceof Double && o2 instanceof Double) {
            return abs((Double) o1 - (Double) o2) < 1e-6;
        } else if (o1 instanceof Number && o2 instanceof Number) {
            return ((Number) o1).longValue() == ((Number) o2).longValue();
        }

        final String s1 = o1 != null ? o1.toString() : "";
        final String s2 = o2 != null ? o2.toString() : "";

        return s1.equals(s2);
    }

    public static Throwable unwrapThrowable(
            Throwable throwable
    ) {
        Throwable c = throwable;
        while (c instanceof ExecutionException || c instanceof InvocationTargetException || c instanceof CompletionException) {
            if (c.getCause() != null) {
                c = c.getCause();
            } else {
                break;
            }
        }
        return c;
    }

    /**
     * 判断当前线程是否是UI线程，如果不是UI线程则打印当前线程的调用栈并立刻退出应用程序。
     *
     * @throws IllegalStateException
     *         如果当前线程不是UI线程。
     */
    public static void assertUIThread() {
        if (!isEventDispatchThread()) {
            final StringBuilder buffer = new StringBuilder();
            final Thread thread = Thread.currentThread();

            buffer.append("current thread {id=").append(thread.getId()).append(",name=").append(thread.getName()).append("} is not event dispatch thread\n");
            for (final StackTraceElement st : thread.getStackTrace()) {
                buffer.append("  at ")
                      .append(st.getClassName()).append(".").append(st.getMethodName())
                      .append("(").append(st.getFileName()).append(":").append(st.getLineNumber()).append(")\n");
            }

            System.err.println(buffer.toString());

            System.exit(-2);
        }
    }

    /**
     * 当前执行线程的异常处理器。
     *
     * @param <T>
     *         返回值的类型。
     * @param throwable
     *         发生的异常。
     * @return {@code null}。
     */
    public static <T> T defaultDumpThrowable(
            Throwable throwable
    ) {
        defaultDumpThrowable(Thread.currentThread(), throwable);
        return null;
    }

    /**
     * 默认的异常处理器。
     *
     * @param thread
     *         发生异常的线程。
     * @param throwable
     *         发生的异常。
     */
    public static void defaultDumpThrowable(
            Thread thread,
            Throwable throwable
    ) {
        if (throwable instanceof Error) {
            throwable.printStackTrace();
            System.exit(-1);
        }

        if (thread != null) {
            System.err.println("thread: {id=" + thread.getId() + ",name=" + thread.getName()
                                       + ",group=" + (thread.getThreadGroup() != null ? thread.getThreadGroup().getName() : "")
                                       + ",state=" + thread.getState() + "}");
        }

        throwable = unwrapThrowable(throwable);

        if (throwable == null) {
            System.err.print("null throwable");
        } else {
            throwable.printStackTrace();
        }
    }

    private static class UpdateUIExecutor
            implements Executor {
        /**
         * {@inheritDoc}
         */
        @Override
        public void execute(
                Runnable command
        ) {
            notNull(command, "command");

            SwingUtilities.invokeLater(() -> {
                try {
                    command.run();
                } catch (Throwable throwable) {
                    // invokeLater方法将执行中的异常截获并包装在 InvokeEvent当中，
                    // 导致外层无法截获异常，所以需要在此处截获并处理。
                    defaultDumpThrowable(throwable);
                }
            });
        }
    }
}
